﻿
namespace n_CWavePanel
{
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

//*****************************************************
//双缓冲显示容器类
public class CWavePanel : Panel
{
	bool isMouseLeftPress;
	bool isMouseRightPress;
	
	int c_V;	//当前的输入数据(对应于显示框的零点)
	int c_N;	//当前的输入数据索引(对应于显示框的当前鼠标位置)
	
	const int RightLabelWidth = 20;
	const int LeftLabelWidth = 50;
	
	//曲线纵轴缩放的比例, 表示为 Y / D, 即一个像素单位和一个数据单位的比例, 当比例为1时, 输入数据的大小和
	//屏幕的像素是一一对应的; 当比例大于1的时候, 可能需要多个像素单位才能显示一个数据单位;
	//当比例小于1的时候, 一个像素单位可能表示多个数据单位;
	float a;
	
	//曲线横轴缩放的比例, 表示为 X / N, 即一个像素单位和一个索引单位的比例, 当比例为1时, 输入数据的索引和
	//屏幕的像素是一一对应的; 当比例大于1的时候, 可能需要多个像素单位才能显示一个索引单位;
	//当比例小于1的时候, 一个像素单位可能表示多个索引单位;
	float rX;
	
	//接收数据的零点在屏幕坐标上的位置, 为零时表示居于屏幕坐标的中点
	int YStart;
	
	int LastY;			//鼠标移动上一次的纵坐标Y
	int CurrentX;		//鼠标移动的当前X坐标
	
	//数据缓冲区的总长度
	const int Length = 100000;
	//全局缓冲区索引
	static int CurrentIndex;
	
	//数据缓冲区
	int[][] DataBufferList;
	//通道数目
	const int CH_NUMBER = 8;
	
	//曲线相邻两点之间的横坐标, 用来调节曲线的X轴缩放
	const int XOffset = 3;
	
	//曲线纵轴标尺的高度
	const int Grid_HeightOffset = 20;
	const int Grid_WidthOffset = 40;
	
	//显示的字体
	Font f;
	Font f0;
	
	//左边栏纵标文字颜色
	Brush LeftLabelBrush;
	
	Color[] ChannelColorList;
	Brush[] ChannelBrushList;
	Pen[] ChannelPenList;
	
	//构造函数
	public CWavePanel(): base()
	{
		f = new Font( "Courrier New", 11 );
		f0 = new Font( "Courrier New", 10 );
		this.BackColor = Color.WhiteSmoke;
		
		this.BorderStyle = BorderStyle.None;
		SetStyle(ControlStyles.UserPaint |
		         ControlStyles.AllPaintingInWmPaint |
		         ControlStyles.OptimizedDoubleBuffer |
		         ControlStyles.ResizeRedraw |
		         ControlStyles.SupportsTransparentBackColor,true);
		
		this.Dock = DockStyle.Fill;
		this.ImeMode = ImeMode.NoControl;
		
		LeftLabelBrush = new SolidBrush( Color.FromArgb( 40, 40, 40 ) );
		
		//初始化通道颜色列表
		ChannelColorList = new Color[ CH_NUMBER ];
		for( int i = 0; i < CH_NUMBER; ++i ) {
			ChannelColorList[ i ] = Color.Gray;
		}
		ChannelColorList[ 0 ] = Color.Red;
		ChannelColorList[ 1 ] = Color.Green;
		ChannelColorList[ 2 ] = Color.Blue;
		ChannelColorList[ 3 ] = Color.Purple;
		
		ChannelColorList[ 4 ] = Color.Yellow;
		ChannelColorList[ 5 ] = Color.Pink;
		ChannelColorList[ 6 ] = Color.CornflowerBlue;
		ChannelColorList[ 7 ] = Color.Black;
		
		//初始化通道画刷列表
		ChannelBrushList = new Brush[ CH_NUMBER ];
		for( int i = 0; i < CH_NUMBER; ++i ) {
			Color c = Color.FromArgb( 100, ChannelColorList[i].R, ChannelColorList[i].G, ChannelColorList[i].B );
			ChannelBrushList[ i ] = new SolidBrush( c );
		}
		//初始化通道画笔列表
		ChannelPenList = new Pen[ CH_NUMBER ];
		for( int i = 0; i < CH_NUMBER; ++i ) {
			ChannelPenList[ i ] = new Pen( ChannelColorList[i], 2 );
		}
		
		isMouseLeftPress = false;
		isMouseRightPress = false;
		
		c_V = 0;
		c_N = 0;
		
		a = 0.5f;
		rX = 1;
		
		DataBufferList = new int[ CH_NUMBER ][];
		for( int i = 0; i < CH_NUMBER; ++i ) {
			DataBufferList[ i ] = new int[ Length ];
		}
		CurrentIndex = 0;
		
		this.MouseDown += new MouseEventHandler( GPanelMouseDown );
		this.MouseUp += new MouseEventHandler( GPanelMouseUp );
		this.MouseMove += new MouseEventHandler( GPanelMouseMove );
	}
	
	public void Init()
	{
		YStart = Height - 20; //注意: h是屏幕高度, 而不是软件显示区高度
	}
	
	//鼠标滚轮事件, 当用户在窗体上滚动鼠标滚轮的时候就会触发这个事件
	public void MWheel(object sender, MouseEventArgs e)
	{
		if( e.Delta < 0 ) {
			a /= 1.1f;
		}
		else {
			a *= 1.1f;
		}
		YStart = (int)(c_V * a) + this.Height / 2;
		
		Invalidate();
	}
	
	//图形框鼠标按下事件, 当用户在窗体上点击鼠标按键的时候就会触发这个事件
	void GPanelMouseDown(object sender, MouseEventArgs e)
	{
		if( e.Button == MouseButtons.Left ) {
			LastY = e.Y;
			isMouseLeftPress = true;
		}
		else {
			c_N = GetN( e.X );
			isMouseRightPress = true;
			CurrentX = e.X;
		}
	}
	
	//图形框鼠标松开事件, 当用户在窗体上松开鼠标按键的时候就会触发这个事件
	void GPanelMouseUp(object sender, MouseEventArgs e)
	{
		if( e.Button == MouseButtons.Left ) {
			isMouseLeftPress = false;
		}
		else {
			isMouseRightPress = false;
		}
	}
	
	//图形框鼠标移动事件, 当用户在窗体上移动鼠标的时候就会触发这个事件
	void GPanelMouseMove(object sender, MouseEventArgs e)
	{
		if( isMouseLeftPress ) {
			c_V = GetD( this.Height / 2 );
			YStart += e.Y - LastY;
			LastY = e.Y;
			Invalidate();
		}
		if( isMouseRightPress ) {
			rX = (float)(e.X - LeftLabelWidth) / (CurrentIndex - c_N);
			CurrentX = e.X;
			Invalidate();
		}
	}
	
	//显示刷新事件, 通过一个定时周期为1毫秒的定时器触发, 在此会判断 isOK 和 isDraw 标志,
	//如果 isOK, 则会添加一组数据并启动图形曲线刷新, 如果 isDraw, 则只启动图形曲线刷新.
	//重绘事件
	protected override void OnPaint(PaintEventArgs e)
	{
		//调整选中点, 当按下鼠标右键时, 无论是否移动鼠标, 都会自动让鼠标选中点保持固定状态
		if( isMouseRightPress ) {
			rX = (float)(CurrentX - LeftLabelWidth) / (CurrentIndex - c_N);
		}
		Graphics g = e.Graphics;
		
		//清空背景
		g.Clear( this.BackColor );
		
		//绘制坐标线和网格
		DrawBack( g );
		
		//绘制所有通道的数据曲线
		for( int i = 0; i < CH_NUMBER; ++i ) {
			int StartX = LeftLabelWidth;
			for( int j = CurrentIndex - 1, n = 0; j > 0; --j, ++n ) {
				int D = DataBufferList[ i ][ j ];
				int LD = DataBufferList[ i ][ j - 1 ];
				
				/*
				if( D >= 32768 ) {
					D-= 65536;
				}
				if( LD >= 32768 ) {
					LD-= 65536;
				}
				*/
				
				int X1 = StartX + (int)(n * rX);
				int X2 = StartX + (int)((n + 1) * rX);
				
				//判断是否需要绘制说明
				int Mode = 50;//(int)( (250 - LeftLabelWidth) / rX);
				if( j % Mode == 0 ) {
					DrawExt( g, X1, GetY( D ), ChannelBrushList[ i ], D.ToString() );
				}
				//绘制波形曲线
				g.DrawLine( ChannelPenList[ i ], X1, GetY( D ), X2, GetY( LD ) );
				
				//判断是否有效显示完成
				if( X1 > this.Width - RightLabelWidth ) {
					break;
				}
			}
		}
		//绘制坐标原点直线
		DrawYL( g );
		
		
		g.DrawString( YStart + "," + Height, f, ChannelBrushList[0], 20, 20 );
	}
	
	//根据输入数据获取对应的坐标
	int GetY( int d )
	{
		return YStart - (int)(d * a);
	}
	
	//根据坐标获取对应的数据
	int GetD( int y )
	{
		return (int)( (YStart - y) / a );
	}
	
	//根据X坐标获取对应的缓冲区索引
	int GetN( int x )
	{
		return CurrentIndex - (int)( (x - LeftLabelWidth) / rX );
	}
	
	//数据添加处理函数
	public void AddChannelData( int Channel, int Data )
	{
		DataBufferList[ Channel ][ CurrentIndex ] = Data;
	}
	
	//绘图结束处理函数
	public void ADShowEnd()
	{
		//索引递增
		++CurrentIndex;
		if( CurrentIndex == Length ) {
			CurrentIndex = 0;
		}
		//触发重绘动作
		Invalidate();
	}
	
	//绘制背景网格和标尺
	void DrawBack( Graphics g )
	{
		int YL = GetY( 0 ) % 20;
		
		//绘制界面上的水平的等间隔的若干行直线
		for( int i = YL; i <= this.Height; i += 20 ) {
			DrawLine( g, i );
		}
	}
	
	//绘制坐标原点直线
	void DrawYL( Graphics g )
	{
		//绘制界面上的中点线
		//g.DrawLine( Pens.Black, 0, this.Height / 2, this.Width - RightLabelWidth, this.Height / 2 );
		
		//绘制输入数据的零点线
		g.DrawLine( Pens.Black, LeftLabelWidth, GetY( 0 ), this.Width - RightLabelWidth, GetY( 0 ) );
	}
	
	//绘制一行标线和标尺, 显示基础数值, 扩展数值及单位等
	void DrawLine(  Graphics g, int y )
	{
		g.DrawLine( Pens.LightGray, LeftLabelWidth, y, this.Width - RightLabelWidth, y );
		int v = GetD( y );
		g.DrawString( v.ToString(), f, LeftLabelBrush, 0, y - 9 );
	}
	
	//在指定坐标处绘制一个数据和单位提示
	void DrawExt(  Graphics g, int X, int Y, Brush b, string mes )
	{
		SizeF size = g.MeasureString( mes, f );
		
		Point[] pt = new Point[ 7 ];
		pt[ 0 ] = new Point( X, Y - 3 );
		pt[ 1 ] = new Point( X - 5, Y - 10 );
		pt[ 2 ] = new Point( X - (int)size.Width / 2, Y - 10 );
		pt[ 3 ] = new Point( X - (int)size.Width / 2, Y - 10 - (int)size.Height );
		pt[ 4 ] = new Point( X + (int)size.Width / 2, Y - 10 - (int)size.Height );
		pt[ 5 ] = new Point( X + (int)size.Width / 2, Y - 10 );
		pt[ 6 ] = new Point( X + 5, Y - 10  );
		int mesX = X - (int)size.Width / 2;
		int mesY = Y - 10 - (int)size.Height;
		
		//g.FillClosedCurve( b, pt, FillMode.Winding, 0.1F );
		//g.DrawClosedCurve( Pens.Black, pt, 0.1F, FillMode.Winding );
		//g.DrawString( mes, f, Brushes.White, mesX, mesY );
		
		//g.FillClosedCurve( Brushes.Gray, pt, FillMode.Winding, 0.1F );
		//g.DrawClosedCurve( Pens.DarkGray, pt, 0.1F, FillMode.Winding );
		//g.DrawString( mes, f, b, mesX, mesY );
		
		g.FillClosedCurve( b, pt, FillMode.Winding, 0.1F );
		//g.DrawClosedCurve( Pens.DarkGray, pt, 0.1F, FillMode.Winding );
		g.DrawString( mes, f, Brushes.White, mesX, mesY );
	}
}
}



